﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace LibBinary.DataSource
{
    public class BinaryFileSource
    {
        private Stream stream;

        public BinaryFileSource(Stream stream)
        {
            this.stream = stream;
        }

        public T Read<T>() where T : struct
        {
            int len = Marshal.SizeOf(typeof(T));
            byte[] data = new byte[len];
            try
            {
                if (stream.Read(data, 0, len) != len)
                {
                    throw new ReadingFormatException("Incomplete data.");
                }
            }
            catch (IOException e)
            {
                throw new ReadingFormatException("IO error.", e);
            }
            return BytesToStruct<T>(data);
        }

        public void Write<T>(T val) where T : struct
        {
            int len = Marshal.SizeOf(typeof(T));
            byte[] data = StructToBytes((object)val, len);
            try
            {
                stream.Write(data, 0, len);
            }
            catch (IOException e)
            {
                throw new ReadingFormatException("IO error.", e);
            }
        }

        private static T BytesToStruct<T>(byte[] bytes)
        {
            IntPtr buffer = Marshal.AllocHGlobal(bytes.Length);
            try
            {
                Marshal.Copy(bytes, 0, buffer, bytes.Length);
                return (T)Marshal.PtrToStructure(buffer, typeof(T));
            }
            catch (Exception e)
            {
                throw new ReadingFormatException("Marshaling error.", e);
            }
            finally
            {
                Marshal.FreeHGlobal(buffer);
            }
        }

        private static byte[] StructToBytes(object structObj, int size)
        {
            IntPtr buffer = Marshal.AllocHGlobal(size);
            try
            {
                Marshal.StructureToPtr(structObj, buffer, false);
                byte[] bytes = new byte[size];
                Marshal.Copy(buffer, bytes, 0, size);
                return bytes;
            }
            catch (Exception e)
            {
                throw new ReadingFormatException("Marshaling error.", e);
            }
            finally
            {
                Marshal.FreeHGlobal(buffer);
            }
        }
    }
}
